/**
 * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "gtest/gtest.h"

#include "test_helper.h"

#include "include/runtime.h"
#include "plugins/ecmascript/runtime/internal_call_params.h"
#include "plugins/ecmascript/runtime/object_factory.h"
#include "plugins/ecmascript/runtime/ecma_vm.h"
#include "plugins/ecmascript/runtime/base/builtins_base.h"
#include "plugins/ecmascript/runtime/ecma_runtime_call_info.h"
#include "plugins/ecmascript/runtime/js_thread.h"
#include "plugins/ecmascript/runtime/js_function.h"
#include "plugins/ecmascript/runtime/global_env.h"
#include "plugins/ecmascript/runtime/js_promise.h"
#include "include/runtime_options.h"

// NOLINTNEXTLINE(google-build-using-namespace)
using namespace ark::ecmascript;

namespace ark::test {
class JSPromiseTest : public testing::Test {
public:
    void SetUp() override
    {
        TestHelper::CreateEcmaVMWithScope(instance_, thread_, scope_);
    }

    void TearDown() override
    {
        TestHelper::DestroyEcmaVMWithScope(instance_, scope_);
    }

protected:
    // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes)
    JSThread *thread_ {nullptr};

private:
    PandaVM *instance_ {nullptr};
    ecmascript::EcmaHandleScope *scope_ {nullptr};
};

TEST_F(JSPromiseTest, CreateResolvingFunctions)
{
    EcmaVM *ecmaVm = thread_->GetEcmaVM();
    JSHandle<GlobalEnv> env = ecmaVm->GetGlobalEnv();
    ObjectFactory *factory = ecmaVm->GetFactory();

    JSHandle<JSTaggedValue> promiseFunc = env->GetPromiseFunction();
    JSHandle<JSPromise> jsPromise =
        JSHandle<JSPromise>::Cast(factory->NewJSObjectByConstructor(JSHandle<JSFunction>(promiseFunc), promiseFunc));
    JSHandle<ResolvingFunctionsRecord> reactions = JSPromise::CreateResolvingFunctions(thread_, jsPromise);
    JSHandle<JSTaggedValue> resolve(thread_, reactions->GetResolveFunction());
    JSHandle<JSTaggedValue> reject(thread_, reactions->GetRejectFunction());
    EXPECT_EQ(resolve->IsCallable(), true);
    EXPECT_EQ(reject->IsCallable(), true);
}

TEST_F(JSPromiseTest, NewPromiseCapability)
{
    EcmaVM *ecmaVm = thread_->GetEcmaVM();
    JSHandle<GlobalEnv> env = ecmaVm->GetGlobalEnv();
    JSHandle<JSTaggedValue> promise = env->GetPromiseFunction();

    JSHandle<PromiseCapability> capbility = JSPromise::NewPromiseCapability(thread_, promise);
    JSHandle<JSPromise> newPromise(thread_, capbility->GetPromise());
    EXPECT_EQ(static_cast<PromiseStatus>(newPromise->GetPromiseState().GetInt()), PromiseStatus::PENDING);

    JSHandle<JSPromiseReactionsFunction> resolve(thread_, capbility->GetResolve());
    JSHandle<JSPromiseReactionsFunction> reject(thread_, capbility->GetReject());
    EXPECT_EQ(resolve.GetTaggedValue().IsCallable(), true);
    EXPECT_EQ(resolve.GetTaggedValue().IsCallable(), true);

    JSHandle<JSPromise> resolvePromise(thread_, resolve->GetPromise());
    JSHandle<JSPromise> rejectPromise(thread_, reject->GetPromise());
    EXPECT_EQ(JSTaggedValue::SameValue(newPromise.GetTaggedValue(), resolvePromise.GetTaggedValue()), true);
    EXPECT_EQ(JSTaggedValue::SameValue(newPromise.GetTaggedValue(), rejectPromise.GetTaggedValue()), true);
}

TEST_F(JSPromiseTest, FullFillPromise)
{
    EcmaVM *ecmaVm = thread_->GetEcmaVM();
    JSHandle<GlobalEnv> env = ecmaVm->GetGlobalEnv();
    JSHandle<JSTaggedValue> promise = env->GetPromiseFunction();
    JSHandle<PromiseCapability> capbility = JSPromise::NewPromiseCapability(thread_, promise);
    JSHandle<JSPromise> newPromise(thread_, capbility->GetPromise());
    EXPECT_EQ(static_cast<PromiseStatus>(newPromise->GetPromiseState().GetInt()), PromiseStatus::PENDING);
    EXPECT_EQ(newPromise->GetPromiseResult().IsUndefined(), true);

    JSHandle<JSTaggedValue> resolve(thread_, capbility->GetResolve());
    JSHandle<JSTaggedValue> undefined(thread_, JSTaggedValue::Undefined());

    auto info = NewRuntimeCallInfo(thread_, resolve, undefined, JSTaggedValue::Undefined(), 1);
    // NOLINTNEXTLINE(readability-magic-numbers)
    info->SetCallArgs(JSTaggedValue(33));
    JSFunction::Call(info.Get());
    EXPECT_EQ(static_cast<PromiseStatus>(newPromise->GetPromiseState().GetInt()), PromiseStatus::FULFILLED);
    EXPECT_EQ(JSTaggedValue::SameValue(newPromise->GetPromiseResult(), JSTaggedValue(33)), true);
}

TEST_F(JSPromiseTest, RejectPromise)
{
    EcmaVM *ecmaVm = thread_->GetEcmaVM();
    JSHandle<GlobalEnv> env = ecmaVm->GetGlobalEnv();
    JSHandle<JSTaggedValue> promise = env->GetPromiseFunction();
    JSHandle<PromiseCapability> capbility = JSPromise::NewPromiseCapability(thread_, promise);
    JSHandle<JSPromise> newPromise(thread_, capbility->GetPromise());
    EXPECT_EQ(static_cast<PromiseStatus>(newPromise->GetPromiseState().GetInt()), PromiseStatus::PENDING);
    EXPECT_EQ(newPromise->GetPromiseResult().IsUndefined(), true);

    JSHandle<JSTaggedValue> reject(thread_, capbility->GetReject());
    JSHandle<JSTaggedValue> undefined(thread_, JSTaggedValue::Undefined());

    auto info = NewRuntimeCallInfo(thread_, reject, undefined, JSTaggedValue::Undefined(), 1);
    // NOLINTNEXTLINE(readability-magic-numbers)
    info->SetCallArgs(JSTaggedValue(44));
    JSFunction::Call(info.Get());
    EXPECT_EQ(static_cast<PromiseStatus>(newPromise->GetPromiseState().GetInt()), PromiseStatus::REJECTED);
    EXPECT_EQ(JSTaggedValue::SameValue(newPromise->GetPromiseResult(), JSTaggedValue(44)), true);
}
}  // namespace ark::test
